from django.db import models
from django.utils.translation import gettext_lazy as _ 
from django.core.validators import NumberIsInRange
from validators import * #IGNORE:W0614
from django.conf import settings


class Country(models.Model):
    title = models.CharField(
        _('title'), 
        maxlength=200, 
        unique=True, 
        db_index=True
    )

    def __str__(self):
        return self.title
    
    class Meta: #IGNORE:W0232
        ordering = ('title',)
        verbose_name = _('country')
        verbose_name_plural = _('countries')

    class Admin: #IGNORE:W0232
        list_display = ('title',)
        search_fields = ('title',)


class City(models.Model):
    title = models.CharField(
        _('title'), 
        maxlength=200, 
        unique=True, 
        db_index=True
    )
    country = models.ForeignKey(Country, verbose_name=_('country'))

    def __str__(self):
        return _("%(city)s, %(country)s") % {
            'city'  : self.title, 
            'country': self.country, 
        }
    
    class Meta: #IGNORE:W0232
        ordering = ('title',)
        verbose_name = _('city')
        verbose_name_plural = _('cities')

    class Admin: #IGNORE:W0232
        list_display = ('title', 'country',)
        list_filter = ('country',)
        search_fields = ('title',)


class Location(models.Model):
    title = models.CharField(
        _('title'), 
        maxlength=200, 
        unique=True, 
        db_index=True
    )
    address = models.CharField(
        _('address'), 
        maxlength=200, 
        db_index=True
    )
    city = models.ForeignKey(City, verbose_name=_('city'))
    ticket_capacity = models.PositiveIntegerField(
        _('ticket capacity'), 
        validator_list=[
            NumberIsInRange(lower=settings.LOCATION_MIN_TICKET_CAPACITY), # At least this many seats
            ]
    )

    def __str__(self):
        return _('"%(title)s" %(address)s %(city)s') % {
            'title' : self.title, 
            'address' : self.address, 
            'city' : self.city, 
        }
    
    class Meta: #IGNORE:W0232
        ordering = ('title',)
        verbose_name = _('location')
        verbose_name_plural = _('locations')

    class Admin: #IGNORE:W0232
        list_display = ('title', 'address', 'city', 'ticket_capacity',)
        list_filter = ('city',)
        search_fields = ('title',)


class Festival(models.Model):
    FEST_TYPES = (
        ('C', _('Competition')), 
        ('R', _('Review')), 
    ) 

    title = models.CharField(
        _('title'), 
        maxlength=200, 
        unique=True, 
        db_index=True
    )
    start_date = models.DateField(
        _('start date'), 
        db_index=True
    )
    end_date = models.DateField(
        _('end date'), 
        db_index=True, 
        validator_list=[
            IsNotBeforeOtherDate('start_date', _('Can not be before the start date.'))
            ]
    )
    fest_type = models.CharField(
        _('festival type'), 
        maxlength=1, 
        choices=FEST_TYPES, 
        db_index=True, 
        radio_admin=True, 
        default=FEST_TYPES[0][0]
    )

    def __str__(self):
        return self.title
    
    class Meta: #IGNORE:W0232
        ordering = ('-start_date', 'title',)
        verbose_name = _('festival')
        verbose_name_plural = _('festivals')

    class Admin: #IGNORE:W0232
        list_display = ('title', 'start_date', 'end_date', 'fest_type',)
        list_filter = ('start_date', 'end_date', 'fest_type',)
        search_fields = ('title',)


class Concert(models.Model):
    CONCERT_TYPES = (
        ('I', _('Invalid type')), 
        ('Q', _('Qualification')), 
        ('F', _('Final')), 
    ) 

    title = models.CharField(
        _('title'), 
        maxlength=200, 
        unique=True, 
        db_index=True
    )
    # FIXME: Must be after festival.start_date
    start_time = models.DateTimeField(
        _('start time'), 
        db_index=True
    )
    # FIXME: Must be before festival.end_date
    end_time = models.DateTimeField(
        _('end time'), 
        db_index=True, 
        validator_list=[
            IsAfterOtherDateTime('end_time', 'start_time', _('Must be after start time.'))
            ]
    )
    festival = models.ForeignKey(Festival, verbose_name=_('festival'))
    tickets_for_sale = models.PositiveIntegerField(
        _('tickets for sale'), 
        validator_list=[
            NumberIsInRange(lower=1), # At least 1 ticket must be sold
            isBellowLocationTicketCapacity, 
            ]
    )
    location = models.ForeignKey(
        Location, 
        verbose_name=_('location'), 
        validator_list=[
            isLocationFree
            ]
    )
    concert_type = models.CharField(
        _('concert type'), 
        maxlength=1, 
        choices=CONCERT_TYPES, 
        db_index=True, 
        radio_admin=True, 
        default='Q', 
        validator_list=[
            IsValidConcertType()
            ]
    )
    judges = models.ManyToManyField(
        'Participant', 
        related_name='judged_concerts', 
        verbose_name=_('judges'), 
        blank=True, 
    )
    hosts = models.ManyToManyField(
        'Participant', 
        related_name='hosted_concerts', 
        verbose_name=_('hosts'), 
    )

    def __str__(self):
        return self.title
    
    class Meta: #IGNORE:W0232
        ordering = ('-start_time', 'title',)
        verbose_name = _('concert')
        verbose_name_plural = _('concerts')

    class Admin: #IGNORE:W0232
        fields = (
            (None, {
                'fields': ('title', 'start_time', 'end_time', 'festival', 'tickets_for_sale', 'location', 'concert_type',)
            }), 
            (_('participants'), {
                'fields' : ('judges', 'hosts',)
            }), 
        )
        list_display = ('title', 'start_time', 'end_time', 'concert_type', 'tickets_for_sale', 'location', 'festival',)
        list_filter = ('start_time', 'end_time', 'concert_type', 'location', 'festival',)
        js = ('../media/js/admin/should-hide-concert-type.js',)
        search_fields = ('title',)


class Song(models.Model):
    title = models.CharField(
        _('title'), 
        maxlength=200, 
        unique=True, 
        db_index=True
    )

    def __str__(self):
        return self.title
    
    class Meta: #IGNORE:W0232
        ordering = ('title',)
        verbose_name = _('song')
        verbose_name_plural = _('songs')

    class Admin: #IGNORE:W0232
        list_display = ('title',)
        search_fields = ('title',)


class Participant(models.Model):
    from django.contrib.auth.models import User

    user = models.ForeignKey(User, verbose_name=_('user'))
    bio = models.TextField(
        _('biography'), 
        db_index=True, 
        help_text=_('Use only plain (nonformatted) text') 
    )

    def get_first_name(self):
        return self.user.first_name #IGNORE:E1101
    get_first_name.short_description = _('first name') #IGNORE:W0612

    def get_last_name(self):
        return self.user.last_name #IGNORE:E1101
    get_last_name.short_description = _('last name') #IGNORE:W0612

    def get_full_name(self):
        return _("%(first_name)s %(last_name)s") % {
            'first_name' : self.get_first_name(), 
            'last_name'  : self.get_last_name(), 
        }
    get_full_name.short_description = _('full name') #IGNORE:W0612
    
    def __str__(self):
        return self.get_full_name()
    
    class Meta: #IGNORE:W0232
        verbose_name = _('participant')
        verbose_name_plural = _('participants')

    class Admin: #IGNORE:W0232
        list_display = ('get_first_name', 'get_last_name',)


class Performance(models.Model):
    concert = models.ForeignKey(
        Concert, 
        verbose_name=_('concert'), 
        edit_inline=True, 
        num_in_admin=6, 
        num_extra_on_change=4, 
    )
    performer = models.ForeignKey(
        Participant, 
        verbose_name=_('performer'), 
        core=True, 
    )
    song = models.ForeignKey(
        Song, 
        verbose_name=_('song'), 
        core=True, 
    )

    def __str__(self):
        return _('"%(song)s" performed by %(performer)s') % {
            'song'      : self.song, 
            'performer' : self.performer, 
        }
    
    def get_score(self):
        # Festival type must not be review
        if self.concert.concert_type == 'I': #IGNORE:E1101
            return None
        
        num_judges = self.concert.judges.count() #IGNORE:E1101
        votes = self.vote_set.all() #IGNORE:E1101

        # There has to be at least 1 judge
        if num_judges == 0:
            return None

        # All judges must vote
        if votes.count() != num_judges:
            return None

        # All is OK, calculate the score
        score = 0
        for vote in votes:
            score += vote.vote
        return score/num_judges
    
    class Meta: #IGNORE:W0232
        # unique_together won't be enforced until db-level => no admin validation error for the user, only the IntegrityError exception
        unique_together = (
            ('concert', 'performer', 'song',), 
        ) 
        verbose_name = _('performance')
        verbose_name_plural = _('performances')


class Vote(models.Model):
    GRADE_CHOICES = map(lambda x: (x, x), range(settings.VOTE_MIN_GRADE, settings.VOTE_MAX_GRADE+1)) #IGNORE:W0141  

    judge = models.ForeignKey(
        Participant, 
        verbose_name=_('performer'), 
    )
    performance = models.ForeignKey(
        Performance, 
        verbose_name=_('performance'), 
    )
    vote = models.PositiveIntegerField(
        _('vote'), 
        choices=GRADE_CHOICES, 
        db_index=True, 
        radio_admin=True, 
    )    

    def __str__(self):
        return _("%(judge)s voted %(vote)s for %(performance)s") % {
            'judge'       : self.judge, 
            'vote'        : self.vote, 
            'performance' : self.performance, 
        }
    
    class Meta: #IGNORE:W0232
        # unique_together won't be enforced until db-level => no admin validation error for the user, only the IntegrityError exception
        unique_together = (
            ('judge', 'performance',), 
        ) 
        verbose_name = _('vote')
        verbose_name_plural = _('votes')